SPB Git

Repository Public MIT

page.tsx 3.3 KB 71 lines tsx

1import type { Metadata } from "next";2import Link from "next/link";3import { notFound } from "next/navigation";4import { Markdown } from "@/components/markdown";5import { DOC_SECTIONS, getDoc, listDocs } from "@/lib/content";67type P = { params: Promise<{ slug?: string[] }> };8const slugOf = (s?: string[]) => (s === undefined || s.length === 0 ? "index" : s.length === 1 ? s[0]! : "");910export async function generateMetadata({ params }: P): Promise<Metadata> {11  const doc = getDoc(slugOf((await params).slug));12  return doc ? { title: `${doc.title} — Docs`, description: doc.description } : {};13}1415export default async function Page({ params }: P) {16  const slug = slugOf((await params).slug);17  const doc = getDoc(slug);18  if (!doc) notFound();19  const docs = listDocs();20  const sections = [...DOC_SECTIONS, ...new Set(docs.map((d) => d.section).filter((s) => !DOC_SECTIONS.includes(s)))];21  const idx = docs.findIndex((d) => d.slug === slug);22  const prev = idx > 0 ? docs[idx - 1] : null;23  const next = idx >= 0 && idx < docs.length - 1 ? docs[idx + 1] : null;24  const href = (s: string) => (s === "index" ? "/docs" : `/docs/${s}`);25  const nav = (26    <ul className="space-y-6">27      {sections.map((sec) => {28        const items = docs.filter((d) => d.section === sec);29        if (!items.length) return null;30        return (31          <li key={sec}>32            <p className="eyebrow mb-2">{sec}</p>33            <ul className="space-y-0.5 border-l border-line">34              {items.map((d) => (35                <li key={d.slug}>36                  <Link href={href(d.slug)} aria-current={d.slug === slug ? "page" : undefined} className={`-ml-px block border-l py-1.5 pl-3 text-sm ${d.slug === slug ? "border-accent text-text" : "border-transparent text-muted hover:text-text"}`}>37                    {d.title}38                  </Link>39                </li>40              ))}41            </ul>42          </li>43        );44      })}45      <li>46        <p className="eyebrow mb-2">Machine-readable</p>47        <a href="/api/v1/openapi.json" className="block py-1.5 text-sm text-muted hover:text-text">OpenAPI 3.1 document</a>48      </li>49    </ul>50  );51  return (52    <div className="container-page grid gap-10 pb-10 pt-10 lg:grid-cols-[14rem_minmax(0,1fr)]">53      <details className="card p-4 lg:hidden">54        <summary className="cursor-pointer text-sm font-medium">Documentation menu</summary>55        <nav aria-label="Documentation (mobile)" className="mt-4">{nav}</nav>56      </details>57      <nav aria-label="Documentation" className="hidden lg:sticky lg:top-20 lg:block lg:max-h-[calc(100dvh-6rem)] lg:self-start lg:overflow-y-auto lg:pr-2">{nav}</nav>58      <article className="min-w-0 max-w-3xl">59        <p className="eyebrow mb-3">{doc.section}</p>60        <h1 className="text-3xl font-semibold tracking-tight sm:text-4xl">{doc.title}</h1>61        {doc.description && <p className="mb-8 mt-3 text-lg text-muted">{doc.description}</p>}62        <Markdown source={doc.body.replace(/^\s*#\s+.*\n/, "")} trusted />63        <nav aria-label="Previous and next" className="mt-12 flex justify-between gap-4 border-t border-line pt-6 text-sm">64          {prev ? <Link className="link" href={href(prev.slug)}>← {prev.title}</Link> : <span />}65          {next ? <Link className="link text-right" href={href(next.slug)}>{next.title} →</Link> : <span />}66        </nav>67      </article>68    </div>69  );70}71
Files main
Menu

Theme